/*  
 * Copyright (c) 2010, James Daniello
 * This project is licensed under the 'New BSD' license.
 * The full text of the license can be found in the root
 * directory under the filename "LICENSE"
 */
package src.gui;

import java.io.PrintWriter;
import java.util.Scanner;
import java.util.HashMap;
import java.io.File;
import java.util.Vector;
import javax.swing.JFrame;
import javax.swing.JApplet;
import src.network.PICAIUClientComm;
import java.util.Iterator;
import java.security.MessageDigest;
import java.util.StringTokenizer;
import java.io.ByteArrayOutputStream;
import javax.swing.SwingUtilities;
import src.objects.PICAIUGameUnit;

/**
 * PICAIUClient is the main class of the PICAIUClient. This class houses the
 * GUI component, as well as the backend part which will communicate with the server.
 * This class also houses methods to facilitate communication between the GUI and backend.
 */
public class PICAIUClient {

  //Server and port used to connect to server
  //public static final String HOST = "localhost";
  //public static final int PORT = 8555;
  public static final boolean VERBOSE = true; /* DEBUGGING. Output data to screen.*/

  public static boolean RUNNINGAPPLET = false;
  //The chat client is used to send data back and forth to/from server.
  private PICAIUClientComm clientComm;
  //The gui component of the client
  private PICAIUClientGUI gui;
  //The name of the user who is connected to the client.
  public String userName;
  public String enemyName;
  //the amount of money the user has
  public int userMoney = 0;
  public boolean isRedTeam=true;

  //Are we actually connected?
  private boolean connected = false;
  private HashMap<String, Integer> serversList = new HashMap<String, Integer>();
  private String selectedServer = "";

  /**
   * This is the main method. It is/was used primarily for debugging and development
   * so that an applet didn't need to be run. It creates a version of the GUI based
   * on a Jframe, rather than an applet, and uses the username specified from the
   * command line.
   */
  public static void main(String[] args) {
    //make sure a name is provided
    //if(args.length!=1){
    //  System.out.println("Wrong usage. Try: java PICAIUClient <userName>");
    //  System.exit(1);
    //} 
    SwingUtilities.invokeLater(new Runnable() {

      public void run() {
        PICAIUClient client = new PICAIUClient();//args[0]);
      }
    });
  }

  /** 
   * Creates a version of the PICAIUClient with the username specified by
   * the only parameter. This method will create it within a JFrame, as opposed
   * to the applet version of the method by the same name and then attempt
   * to connect to the server.
   * @param userNameIn   the desired username
   */
  public PICAIUClient() {

    //basic JFrame GUI  
    JFrame frame = new JFrame("PICAIU Client");
    gui = new PICAIUClientGUI(frame, this);
    frame.addKeyListener(gui);
    frame.getContentPane().addMouseListener(gui);
    loadServers("");
    setGUI("login");
    //setGUI("engine");
    //setGUI("pregame");

    // setGUI("no server file");


  }

  /**
   * Create a version of the PICAIUClient with the username specified by
   * the userNameIn paramter. This version of the method takes an applet
   * and creates a GUI within the applet. Then it attempts to connect
   * to the server.
   * @param applet       the parent applet
   * @param userNameIn   the desired username
   */
  public PICAIUClient(JApplet applet) {

    //basic gui 
    gui = new PICAIUClientGUI(applet.getContentPane(), this);
    applet.addKeyListener(gui);
    applet.getContentPane().addMouseListener(gui);
    RUNNINGAPPLET = true;
    loadServers("");
    setGUI("login");
    //setGUI("engine");

    // setGUI("no server file");

  }

  /**
   * This method saves the server list to the file 'server_list.txt'
   */
  public void saveServerList() {
    try {
      File f = new File("server_list.txt");
      if (!f.exists()) {
        f.createNewFile();
      }
      PrintWriter output = new PrintWriter(f);

      output.println("//do not edit this file unless you know what you are doing.");

      Iterator i = serversList.keySet().iterator();
      while (i.hasNext()) {
        String server = i.next().toString();
        output.println(server + " " + serversList.get(server));
      }

      output.flush();
      output.close();
    } catch (Exception e) {
      System.out.println("May have failed to save file 'server_list.txt'");
      System.out.println(e);
    }

  }

  /**
   * This method loads all of the servers from the appropriate file and puts them into the client.
   * It then returns whether any were found.
   */
  private boolean loadServers(String dir) {
    try {

      File f = new File("server_list.txt");
      if (!f.exists()) {
        f.createNewFile();
      }

      Scanner sc = new Scanner(new File(dir + "server_list.txt"));
      boolean serverFound = false;
      while (sc.hasNextLine()) {
        String line = sc.nextLine();
        if (!line.startsWith("//")) {
          String[] splitLine = line.split(" ");
          if (splitLine.length > 1) {
            if (!serverFound) {
              selectedServer = splitLine[0];
            }
            serverFound = true;
            serversList.put(splitLine[0], Integer.parseInt(splitLine[1]));
          }
        }
      }
      sc.close();
      return serverFound;
    } catch (Exception e) {
      System.out.println(e);
      return false;
    }
  }

  /**
   * Returns the list of all the servers loaded in from 'server_list.txt'
   */
  protected HashMap<String, Integer> getServers() {
    return serversList;
  }

  /**
   * Removes the specified server from the list of servers
   * @param server the server to be removed
   */
  public void removeServer(String server) {
    if (serversList.containsKey(server)) {
      serversList.remove(server);
    }
  }

  /**
   * Adds the specified server to the list of servers
   * @param host the host, devoid of spaces, of the server
   * @param port the port, in whole number integers, of the server
   */
  public void addServer(String host, int port) {
    if (!serversList.containsKey(host)) {
      serversList.put(host, port);
    }
  }

  /**
   * This method updates the selected server
   */
  protected void updateSelectedServer(String selectedServerIn) {
    selectedServer = selectedServerIn;
    disconnectFromServer();
    if (!selectedServer.equals("") && selectedServer != null && serversList.containsKey(selectedServer)) {
      connect();
    }
  }

  /**
   * Returns the first server in the 'server_list.txt' file. This first server was
   * the previously connected-to server
   */
  protected String getSelectedServer() {
    return selectedServer;
  }

  /**
   * Part of the initialization of PICAIUClient. Here the backend is
   * created which is used to communicate with the server. It is housed within
   * its own thread so that it can sleep and doesn't hog resources.
   * @param HOST the connecting host
   * @param PORT the connecting port
   * @param AUTH the authentication data
   */
  public void connect() {
    String HOST = selectedServer;
    int PORT = serversList.get(selectedServer);

    System.out.println("Attempting to connect");
    clientComm = new PICAIUClientComm(HOST, PORT, this);
    Thread t = new Thread(clientComm);
    t.start();

  }

  /**
   * This method sends authentication data to server. The username and password are sent.
   * One or both may be encrypted
   * @param username the desired username
   * @param password the username's password
   */
  protected void authenticate(String username, String password) {
    sendDataToServer("SETNAME#" + username + "#" + password);
  }

  /**
   * This sets the current user in the Login screen
   */
  public void setLoginUser(String userIn) {
    gui.setLoginUser(userIn);
  }

  /**
   * Tells to gui to change which component is currently being displayed
   */
  public void setGUI(String guiIn) {
    gui.setGUI(guiIn);
  }

  /**
   * This method acts on which error message is sent. This may be optimized later so it doesn't input
   * a string, but rather a short, or a byte.
   */
  public void error(String error) {
    if (error.equals("DB")) {
      gui.error("DB");
    }
  }

  /**
   * Tells the backend to send the desired message to the server.
   * Most likely this method is being called from the GUI when the user
   * wants to send a message.
   * This method is exactly like sendDataToServer, except the control string
   * for text will be appended to the message.
   * @param message  the message to send to the server
   */
  protected void sendMessageToServer(String message) {
    clientComm.sendMessageToServer(message);
  }

  /** 
   * Tells the backend to send the desired data to the server.
   * Most likely this method is being called from the GUI when the user
   * wants to send a message.
   * This method is exactly like sendMessageToServer, except the control string
   * for data will be appended to the message.
   * @param message  the message to send to the server
   */
  protected void sendDataToServer(String message) {
    clientComm.sendDataToServer(message);
  }

  /**
   * Displays the given message to the gui.
   * @param message  the message to display to the screen
   */
  public void writeToLobby(String message) {
    gui.writeToLobby(message);
  }

  /**
   * Returns the username associated with this client
   * @return userName  the name of the user running this client
   */
  public String getUserName() {
    return userName;
  }

  /**
   *  Sets the name of the user running this client
   */
  public void setUserName(String userNameIn) {
    userName = userNameIn;
  }

  /**
   * Used to clear the list of users connected to the game lobby.
   * This method is likely called when the client becomes disconnected.
   */
  public void clearUserList() {
    gui.clearUserList();
  }

  /**
   * This method, when called from the clientComm will tell the
   * GUI that a user is in the lobby and needs to be displayed.
   * @param name   the name of the user who has connected
   */
  public void addUserToList(String name) {
    gui.addUserToList(name);
  }

  /**
   * This method, when called from the clientComm will tell the
   * GUI that a user has left the lobby and the change needs to be
   * displayed.
   * @param name   the name of the user who has connected
   */
  public void removeUserFromList(String name) {
    gui.removeUserFromList(name);
  }

  /**
   * This method will tell the GUI to remove a user with
   * the username used by this client to be removed visually.
   * This method will likely never be used. It was used during
   * development but not removed.
   */
  protected void removeSelfFromList() {
    gui.removeSelfFromList();
  }

  /**
   * This method tells the backend to let the server know
   * that it wishes to disconnect.
   */
  protected void disconnectFromServer() {
    if (clientComm != null && clientComm.isRunning()) {
      clientComm.disconnectFromServer();
    }
  }

  /**
   * Used to set the client as connected or disconnected.
   */
  public void setConnected(boolean connectedIn) {
    connected = connectedIn;
  }

  /**
   * Enables the login fields on the login screen
   */
  public void enableLogin() {
    gui.enableLogin();
  }

  /**
   * Enables registration again with the specified error message
   */
  public void enableRegistration(String error) {
    gui.enableRegistration(error);
  }

  /**
   * Indicates that a recover of username failed
   */
  public void recoverFailed(String error) {
    gui.recoverFailed(error);
  }

  /**
   * Indicates that a password change has failed
   */
  public void changeFailed(String error) {
    gui.changeFailed(error);
  }

  /**
   * communicates a special message to the resetPassword2 class
   */
  public void setResetPassword2Email(String email) {
    gui.setResetPassword2Email(email);
  }

  /**
   * Returns whether or not the client thinks it is currently connected.
   * @return connected   is client connected?
   */
  public boolean isConnected() {
    return connected;
  }

  /**
   * Tells the client to disconnect from the server and clear
   * out the GUI then terminate the program.
   */
  public void exitClient() {
    if (clientComm != null && clientComm.isRunning()) {
      clientComm.disconnectFromServer();
    }
    gui.disposeOfFrame();
    System.exit(1);
  }

  /**
   * This method returns a string given an array of bytes. Code borrowed from Java Reference Guide.
   */
  public static String getString(byte[] bytes) {
    StringBuffer sb = new StringBuffer();
    for (int i = 0; i < bytes.length; i++) {
      byte b = bytes[i];
      sb.append((int) (0x00FF & b));
      if (i + 1 < bytes.length) {
        sb.append("-");
      }
    }
    return sb.toString();
  }

  /**
   * This method returns an array of bytes given a string. Code borrowed from Java Reference Guide.
   */
  public static byte[] getBytes(String str) {
    ByteArrayOutputStream bos = new ByteArrayOutputStream();
    StringTokenizer st = new StringTokenizer(str, "-", false);
    while (st.hasMoreTokens()) {
      int i = Integer.parseInt(st.nextToken());
      bos.write((byte) i);
    }
    return bos.toByteArray();
  }

  /**
   * This method md5 hash a string and returns it's hashed counterpart
   */
  public static String md5(String source) {
    try {
      MessageDigest md = MessageDigest.getInstance("MD5");
      byte[] bytes = md.digest(source.getBytes());
      return getString(bytes);
    } catch (Exception e) {
      e.printStackTrace();
      return null;
    }
  }

  //sets the user info for the client
  public void setMyMoney(String money) {
    String toSet = " $" + money;
    if (money.equals("")) {
      toSet = "";
    }
    gui.setMyInfo(userName + "" + toSet);
    userMoney = Integer.parseInt(money);
  }

  //sets the user info for the client
  public void setEnemyMoney(String name, String money, String otherReady) {
    gui.setEnemyInfo(name + " $" + money, otherReady);
    gui.setEnemyMoney(Integer.parseInt(money));
    enemyName = name;
  }

  public void setGameTypes(String string) {
    gui.setGameTypes(string);
  }

  public void enableGameCreation() {
    gui.enableGameCreation();
  }

  public int getUserMoney() {
    return userMoney;
  }

  public void addGame(String persona, String amount) {
    gui.addGameToList(persona + " >> " + "[$" + amount + "]");
  }

  public void addGame(String string) {
    gui.addGameToList(string);
  }

  public void removeGame(String string) {
    gui.removeGameFromList(string);
  }

  public void otherUserLeftPreGame() {
    gui.otherUserLeftPreGame();
  }

  public void clearGamesList() {
    gui.clearGamesList();
  }

  public void addUnit(String id, String type, String worth, String health, String energy, String vision, String accuracy) {
    gui.addUnit(id, type, worth, health, energy, vision, accuracy);
  }

  public void setTeam(String team) {
    if(team.equals("BLUE")){
      isRedTeam=false;
      gui.setRedTeam(isRedTeam);
    }else{
      isRedTeam=true;
      gui.setRedTeam(isRedTeam);
    }
  }

  /**
   * Notifies this user that the other user is ready
   */
  public void otherReady() {
    gui.otherReady();
  }

  public void removeUnitFromPreGame(String id, String type) {
    gui.removeUnitFromPreGame(id, type);
  }

  public void setNumberOfExpectedUnits(int num) {
    gui.setNumberOfExpectedUnits(num);
  }

  public void clearPreGame() {
    gui.clearPreGame();
  }

  public void initiateEngine() {
    gui.initiateEngine();
  }

  public void addIncomingUnit(String unit) {
    gui.addIncomingUnit(unit);
  }

  void requestGamesList() {
    sendDataToServer("GETGMSLIST");
  }


  public void sendPositions(Vector<PICAIUGameUnit> units) {
    for(PICAIUGameUnit u:units){
      sendDataToServer("FP#"+u.getId()+"#"+u.getX()+"#"+u.getY());
    }
  }

  public void setInitialEnemyPosition(String team, long id, float x, float y) {
    gui.setInitialEnemyPositions(team,id,x,y);

  }

  public void startEngine() {
    gui.startEngine();
  }

  public void sendBounty(String team, int x, int y, int type, int value, long attachedUnit) {
   this.sendDataToServer("BOU#"+team+"#"+x+"#"+y+"#"+type+"#"+value+"#"+attachedUnit);
  }

  public void updateUnit(String message) {
    gui.updateUnit(message);
  }

  public boolean isInGame() {
    return gui.isInGame();
  }

  String getEnemyName() {
    return enemyName;
  }

}
